package com.benmei.weike.common.filter;

import com.benmei.weike.common.Constants;
import com.benmei.weike.common.Lang;
import com.benmei.weike.dao.SystemAccessLogDao;
import com.benmei.weike.dao.TdNtMemberDao;
import com.benmei.weike.dao.TdNtTeacherDao;
import com.benmei.weike.entity.SystemAccessLog;
import com.benmei.weike.service.common.MemcachedService;
import com.benmei.weike.util.IPUtil;
import com.benmei.weike.util.JsonUtil;
import com.benmei.weike.util.MD5Util;
import com.nativetalk.base.RetInfo;
import com.nativetalk.bean.member.TdNtMember;
import com.nativetalk.bean.teacher.TdNtTeacherResult;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * 登录控制过滤器
 * Created by Peter on 2017/4/15.
 */

@Component
public class SignFilter implements Filter {
    public static final String GET_INJECT_STR = "(function(,script,iframe,insert,delete,update,declare,expression,XSS,alert,scanner,onerror,prompt,atestu,object,having,eval,../,./,<!--,-->";

    public static final String POST_INJECT_STR = "(function(,script,iframe,insert,delete,update,declare,expression,XSS,alert,scanner,onerror,prompt,atestu,object,having";

    /**
     * GET请求非法字符集合
     */
    public static final String[] GET_INJECT_STRs;

    /**
     * POST请求非法字符集合
     */
    public static final String[] POST_INJECT_STRs;
    public static final String KEY = "nativetalk";

    static {
        GET_INJECT_STRs = GET_INJECT_STR.split(",");
        POST_INJECT_STRs = POST_INJECT_STR.split(",");
    }


    private Logger logger = LoggerFactory.getLogger(SignFilter.class);


    @Autowired
    MemcachedService memcachedService;

    @Autowired
    TdNtTeacherDao tdNtTeacherDao;

    @Autowired
    TdNtMemberDao tdNtMemberDao;

    @Autowired
    SystemAccessLogDao systemAccessLogDao;


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("init");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        logger.info("doFilter start ------------------------>>>>>>");
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=UTF-8");
        try {
            //打印 HTTP Header
            printHttpHeader(request);

            String url = request.getRequestURL().toString();
            if (url.indexOf("html") > 0) {//web页面请求】
                logger.info("------------ 页面请求 -----------");
            } else if (url.indexOf("/pay/notify") > 0) {
                logger.info("------------ Ping ++ Notify -----------");
            } else if (url.indexOf("/WeiChatPay/notify") > 0) {
                logger.info("------------ WeiChatPay Notify -----------");
            } else if (url.indexOf("/jhf/notify") > 0) {
                logger.info("------------惠分期 Notify -----------");
            } else if (url.indexOf("static") > 0) {//web页面请求】
                logger.info("------------ 静态资源请求 -----------");
            } else {
                logger.info("------------ API请求 -----------");

                //api请求
                //2 验证请求头信息
                String timestamp = request.getHeader("Timestamp");//单位秒
                if (StringUtils.isBlank(timestamp)) {
                    String msg = "Timestamp不能为空";
                    String code = "502";
                    errorResponse(response, RetInfo.getClientErrorInfo(code, msg));
                    return;
                }
                String signInfo = request.getHeader("SignInfo");
                if (StringUtils.isBlank(signInfo)) {
                    String msg = "SignInfo不能为空";
                    String code = "503";
                    errorResponse(response, RetInfo.getClientErrorInfo(code, msg));
                    return;
                }
                String clientType = request.getHeader("client_type");
                if (StringUtils.isBlank(clientType) && !url.contains("/v4.2/student/member/register/bycode") && !url.contains("/mcCourse/weixinPage/invitation")) {
                    String msg = "client_type不能为空";
                    String code = "504";
                    errorResponse(response, RetInfo.getClientErrorInfo(code, msg));
                    return;
                }


                //3. 判断客户端类型是否有误1 android ; 2 ios
                if (!Constants.DeviceType.Android.equals(clientType) && !Constants.DeviceType.IOS.equals(clientType) && !Constants.DeviceType.Weixin.equals(clientType) && !Constants.DeviceType.Web.equals(clientType) && !url.contains("/v4.2/student/member/register/bycode")) {
                    String msg = "客户端类型有误";
                    String code = "505";
                    errorResponse(response, RetInfo.getClientErrorInfo(code, msg));
                    return;
                }

                //4. 判断是否是恶意刷屏 同一个IP 2小时之内访问次数不能超过10W次
                Map<String, Integer> map = new HashMap<>();
                String ip = IPUtil.getIp(request);
                Object accessFrequencies = memcachedService.get(ip);
                if (accessFrequencies != null) {
                    map = (Map<String, Integer>) accessFrequencies;
                    int size = map.get("size") + 1;
                    logger.info("The ip " + ip + " request number of times:" + size);
                    if (size >= 100000) {
                        String msg = "请求过于频繁";
                        String code = "506";
                        errorResponse(response, RetInfo.getClientErrorInfo(code, msg));
                        return;
                    } else {
                        map.put("size", size);
                        memcachedService.replace(ip, map, 2 * 60 * 60);
                    }
                } else {
                    map.put("size", 1);
                    logger.info("The ip " + ip + " request number of times:" + 1);
                    memcachedService.add(ip, map, 2 * 60 * 60);
                }

                Long s = (System.currentTimeMillis() / 1000 - Long.valueOf(timestamp)) / 60;//单位为分钟
                //5. 判断时间戳是否过期  时长5分钟
                if (s > 60 * 24 * 30) {
                    String msg = "签名超时";
                    String code = "507";
                    errorResponse(response, RetInfo.getClientErrorInfo(code, msg));
                    return;
                }

                //6. 判断加密信息是否正确
                String plaintext = timestamp + KEY;           //明文
                String ciphertext = MD5Util.code(plaintext);  //密文
                if (!signInfo.equals(ciphertext)) {
                    String msg = "签名无效";
                    String code = "508";
                    errorResponse(response, RetInfo.getClientErrorInfo(code, msg));
                    return;
                }

                /**
                 * token的作用，token相当于session，用于维持会话；
                 * 1. 当用户登录系统时，将用户的token放到memcached中;
                 * 2. 当用户退出时  a: 将token从memcached删除; b: 重新生成token; c:更新老师状态为下线。
                 */
                //7. 老师的token校验

                if (url.indexOf("/teatoken") > 0) {

                    //判断token是否为空
                    String token = request.getHeader("token");
                    if (StringUtils.isBlank(token)) {
                        //没有token字段，让用户重新登录，ios客户端如果拿到token会带上token
                        errorResponse(response, RetInfo.getReloginInfo(Lang.EN));
                        return;
                    }

                    Object o_teacher = memcachedService.get(token);
                    TdNtTeacherResult tdNtTeacher = null;
                    //如果缓存中没有token，那么需要查询数据库
                    if (o_teacher == null) {
                        //从数据库中查询用户信息
                        tdNtTeacher = tdNtTeacherDao.findTdNtTeacherByToken(token);
                        //如果缓存和数据库中都没有该token对应的用户，那么用户需要用户重新登录系统
                        if (tdNtTeacher == null) {
                            errorResponse(response, RetInfo.getReloginInfo(Lang.EN));
                            return;
                        }//缓存中不存在，但是数据库中存在，将数据库中的token加入缓存
                        else {
                            memcachedService.add(tdNtTeacher.getToken(), tdNtTeacher, 20 * 24 * 60 * 60);
                        }
                    }
                    //如果缓存中有token，那么需要做类型检测，如果类型不是 TdNtTeacherResult，那么仍然需要用户从新登陆系统。
                    //为什么根据token从缓存中查出的数据不是TdNtMember类型？这种可能性很小，比如更换缓存客户端，而他们使用的序列化机制有不一样，那么就会有类型不一致的问题。
                    else {
                        if (!TdNtTeacherResult.class.isInstance(o_teacher)) {
                            RetInfo retInfo = RetInfo.getReloginInfo(Lang.EN);
                            logger.info("缓存的token不是TdNtTeacherResult类型:[" + token + "]，用户需要重新登录系统。 retInfo-->" + retInfo);
                            errorResponse(response, RetInfo.getReloginInfo(Lang.EN));
                            return;
                        } else {
                            tdNtTeacher = (TdNtTeacherResult) o_teacher;
                            memcachedService.replace(tdNtTeacher.getToken(), tdNtTeacher, 20 * 24 * 60 * 60);
                        }
                    }

                }

                //8. 学生的token校验
                if (url.indexOf("/token") > 0) {
                    //判断token是否为空
                    String token = request.getHeader("token");
                    if (StringUtils.isBlank(token)) {
                        //没有token字段，让用户重新登录，ios客户端如果拿到token会带上token
                        errorResponse(response, RetInfo.getReloginInfo(Lang.ZH));
                        return;
                    }

                    Object o_member = memcachedService.get(token);
                    TdNtMember tdNtMember = null;
                    //如果缓存中没有token，那么需要查询数据库
                    if (o_member == null) {
                        //从数据库中查询用户信息
                        tdNtMember = tdNtMemberDao.findTdNtMemberByToken(token);
                        //如果缓存和数据库中都没有该token对应的用户，那么用户需要用户重新登录系统
                        if (tdNtMember == null) {
                            errorResponse(response, RetInfo.getReloginInfo(Lang.ZH));
                            return;
                        }
                        //如果缓存中不存在但是数据库中存在，将数据库中的用户信息加入缓存（应为有可能缓存因重启导致丢失），这里会有一个bug导致20天未登录的用户仍不需要登录
                        else {
                            memcachedService.add(tdNtMember.getToken(), tdNtMember, 20 * 24 * 60 * 60);
                        }
                    }
                    //如果缓存中有token，那么需要做类型检测，如果类型不是 TdNtMember，那么仍然需要用户重新登陆系统。
                    //为什么根据token从缓存中查出的数据不是TdNtMember类型？这种可能性很小，比如更换缓存客户端，而他们使用的序列化机制不一样时，那么就会有类型不一致的问题。
                    else {
                        if (!TdNtMember.class.isInstance(o_member)) {
                            RetInfo retInfo = RetInfo.getReloginInfo(Lang.EN);
                            logger.info("缓存的token不是TdNtMember类型:[" + token + "]，用户需要重新登录系统。 retInfo-->" + retInfo);
                            errorResponse(response, RetInfo.getReloginInfo(Lang.ZH));
                            return;
                        } else {
                            //最后更新缓存中的用户数据，过期时间从新设置为20天，即：用户20天未进入app则需要重新登录系统
                            tdNtMember = (TdNtMember) o_member;
                            memcachedService.replace(tdNtMember.getToken(), tdNtMember, 20 * 24 * 60 * 60);
                        }

                    }
                }

            }

            logger.info("sign filter >>>> success  ");

            chain.doFilter(request, response);
            // TODO  这块代码不起做用，下周回来查找原因 ----------
            // 如果是404，转换响应内容为 RetInfo格式，不让客户端看到tomcat默认的404页面
            if (HttpStatus.SC_NOT_FOUND == response.getStatus()) {
                String uri = request.getRequestURI();
                String method = request.getMethod();
                response.setStatus(HttpStatus.SC_OK);
                errorResponse(response, RetInfo.getClientErrorInfo("404", "您访问的接口不存在，请检查接口地址[" + method + " " + uri + "]"));
                return;
            }

            logger.info("doFilter end <<<<<<------------------------\n");
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            errorResponse(response, RetInfo.getServerErrorInfo());
            return;
        }

    }

    @Override
    public void destroy() {
        logger.info("destroy");
    }

    private void printHttpHeader(HttpServletRequest request) throws IOException {
        SystemAccessLog pojo = new SystemAccessLog();
        Enumeration headerNames = request.getHeaderNames();
        String uri = request.getRequestURI();
        String method = request.getMethod();
        String clientIp = IPUtil.getIp(request);
        String msg = new String("\n----------------  Http Header [" + clientIp + "]---------------\n");
        msg = msg + method + " " + uri + "\n";
        pojo.setCip(clientIp);
        pojo.setUrl(uri);
        pojo.setCreate_date(new Date());

        StringBuffer header = new StringBuffer();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            header.append(key + ":" + value + "\n");
        }
        pojo.setHeader(header.toString());
        header.append("\n");
        msg = msg + header.toString();
        logger.info(msg);

        try {
            systemAccessLogDao.insertSelective(pojo);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }

    }

    private void errorResponse(HttpServletResponse response, RetInfo retInfo) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter out = response.getWriter();
        String json = JsonUtil.toJson(retInfo);
        logger.info(json);
        out.println(json);
        out.flush();
        out.close();
    }


    public static void main(String[] args) {
        String msg = "输入项中不能包含非法字符。";
        String code = "101";

        String retStr = "{\"tip\":" + "\"" + msg + "\"" + ",\"mark\":\"" + code + "\"}";
        System.out.println(retStr);
    }
}
